To be able to edit code and run cells, you need to run the notebook yourself. Where would you like to run the notebook?

This notebook takes about 20 seconds to run.

In the cloud (experimental)

Binder is a free, open source service that runs scientific notebooks in the cloud! It will take a while, usually 2-7 minutes to get a session.

On your computer

(Recommended if you want to store your changes.)

  1. Copy the notebook URL:
  2. Run Pluto

    (Also see: How to install Julia and Pluto)

  3. Paste URL in the Open box

Frontmatter

If you are publishing this notebook on the web, you can set the parameters below to provide HTML metadata. This is useful for search engines and social media.

Author 1

Scrubbable numbers

Try clicking and dragging the numbers in the text below:

👀 Reading hidden code
221 μs



👀 Reading hidden code
104 μs
👀 Reading hidden code
# using PlutoUI
7.3 μs

If Alice has 20 apples, and she gives 3 apples to Bob...

👀 Reading hidden code
md"""
_If Alice has $(@bind a Scrubbable(20)) apples,
and she gives $(@bind b Scrubbable(3)) apples to Bob..._
"""
287 ms

...then Alice has 17 apples left.

👀 Reading hidden code
md"""
_...then Alice has **$(a - b)** apples left._
"""
393 μs


















👀 Reading hidden code
128 μs

Examples

We define a variable x as a scrubbable number like so:

👀 Reading hidden code
260 μs
5
@bind x Scrubbable(5)
👀 Reading hidden code
594 μs
1005
1000 + x
👀 Reading hidden code
12.7 μs

Using interpolation (with $), you can write this definition inside Markdown text:

👀 Reading hidden code
180 μs

If Alice has 20 apples...

md"""
If Alice has $(@bind num_apples Scrubbable(20)) apples...
"""
👀 Reading hidden code
856 μs
20
num_apples
👀 Reading hidden code
7.7 μs

Arguments

Besides an initial value, a scrubbable number also has an array of possible values that can be reached.

When you pass a single number to Scrubbable, this array is automatically created. Have a look at the minimum and maximum allowed values of the following scrubbables:

👀 Reading hidden code
317 μs
(Scrubbable(5), Scrubbable(2000), Scrubbable(30.0))
👀 Reading hidden code
10.0 ms

(Here we created a Scrubbable on its own, without binding its value to a variable. Not very useful!)

You can also specify the array manually:

👀 Reading hidden code
305 μs
220
Scrubbable(200 : 300; default=220)
👀 Reading hidden code
10.2 ms

If no default is specified, the middle value is used.

👀 Reading hidden code
192 μs

Formatting

The library d3-format is used to format floating-point numbers. You can specify a format string like ".2f" to be used to format the scrubbable value. Have a look at their documentation to see more examples.

👀 Reading hidden code
352 μs

I spent € 30M on Pluto.jl stickers

And it was worth it!

👀 Reading hidden code
70.8 ms
you are 🌝 90% cool
👀 Reading hidden code
19.1 ms
👀 Reading hidden code
4.5 ms
👀 Reading hidden code
101 μs

Appendix

What is a good default range?

👀 Reading hidden code
211 μs
default_range (generic function with 1 method)
function default_range(x::Integer)
if x == 0
-10:10
elseif 0 < x <= 10
0:10
elseif -10 <= x < 0
-10:0
else
sort(round.([Int64], (0.0:0.1:2.0) .* x))
end
end
👀 Reading hidden code
881 μs
up_or_down_one_order_of_magnitude = 10 .^ (-1.0:0.1:1.0)
👀 Reading hidden code
56.9 ms
default_range (generic function with 2 methods)
function default_range(x::Real) # not an integer
if x == 0
-1.0 : 0.1 : 1.0
elseif 0 < x < 1
0 : 0.1 : 1
else
sort(x .* up_or_down_one_order_of_magnitude)
end
end
👀 Reading hidden code
685 μs

Integer inputs become integer ranges with constant step:

👀 Reading hidden code
167 μs
default_range(20)
👀 Reading hidden code
32.8 ms
default_range(2000)
👀 Reading hidden code
32.2 μs

Floating points get a logarithmic scale across one order of magnitude:

👀 Reading hidden code
178 μs
default_range(1.0e6)
👀 Reading hidden code
122 ms
default_range(-2.0)
👀 Reading hidden code
20.2 μs

Zero becomes a range around zero:

👀 Reading hidden code
164 μs
-10:10
default_range(0)
👀 Reading hidden code
10.3 μs
-1.0:0.1:1.0
default_range(0.0)
👀 Reading hidden code
13.2 μs

Definition

👀 Reading hidden code
155 μs
Scrubbable
begin
"""
An inline number that can be changed by clicking and dragging the mouse.
# Examples
```julia
md\"\"\"
_If Alice has \$(@bind a Scrubbable(20)) apples,
and she gives \$(@bind b Scrubbable(3)) apples to Bob..._
\"\"\"
```
```julia
md\"\"\"
_...then Alice has **\$(a - b)** apples left._
\"\"\"
```
In the examples above, we give the **initial value** as parameter, and the reader can change it to be lower or higher.
## Custom range
Besides an initial value, a scrubbable number also has an array of possible values that can be reached. When you pass a **single number** to `Scrubbable`, this array is automatically created.
You can also **specify the array manually**:
```julia
@bind apples Scrubbable(200 : 300; default=220)
```
## Formatting

The library [`d3-format`](https://github.com/d3/d3-format) is used to format floating-point numbers. You can specify a **format string** like `".2f"` to be used to format the scrubbable value. Have a look at their [documentation](https://github.com/d3/d3-format) to see more examples.
`@bind money Scrubbable(30e6, format=".0s", prefix="€ ")`
`@bind coolness Scrubbable(0.80 : 0.01 : 1.00, format=".0%", prefix="you are 🌝 ", suffix=" cool")`
"""
Base.@kwdef struct Scrubbable
values::AbstractVector{<:Real}
default::Real
format::Union{AbstractString,Nothing}=nothing
prefix::AbstractString=""
suffix::AbstractString=""
end
Scrubbable(range::AbstractVector; kwargs...) = Scrubbable(;values=range, default=range[1 + length(range) ÷ 2], kwargs...)
Scrubbable(x::Number; kwargs...) = Scrubbable(;values=default_range(x), default=x, kwargs...)
Base.get(s::Scrubbable) = s.default
function Base.show(io::IO, m::MIME"text/html", s::Scrubbable)
format = if s.format === nothing
# TODO: auto format
if eltype(s.values) <: Integer
""
else
".1f"
end
else
String(s.format)
end

write(io, """<script>

const d3format = await import("https://cdn.jsdelivr.net/npm/d3-format@2/+esm")

const argmin = xs => xs.indexOf(Math.min(...xs))
const closest_index = (xs, y) => argmin(xs.map(x => Math.abs(x-y)))

const values = $(string(collect(s.values)))

const el = html`
<span title="Click and drag this number left or right!" style="cursor: col-resize;
touch-action: none;
background: rgb(252, 209, 204);
padding: 0em .2em;
border-radius: .3em;
font-weight: bold;">$(s.default)</span>
`



let old_x = 0
let old_index = 0
let current_index = closest_index(values, $(s.default))

const formatter = s => $(repr(s.prefix)) + d3format.format($(repr(format)))(s) + $(repr(s.suffix))



Object.defineProperty(el, 'value', {
get: () => values[current_index],
set: x => {
current_index = closest_index(values, x)
el.innerText = formatter(el.value)
},
});

// initial value
el.innerText = formatter($(s.default))

const onScrub = (e) => {
const offset = e.clientX - old_x
const new_index = Math.min(values.length-1, Math.max(0,
Math.round(offset/10) + old_index
))

if(new_index !== current_index) {
current_index = new_index
el.innerText = formatter(el.value)
el.dispatchEvent(new CustomEvent("input"))
}
}

const onpointerdown = (e) => {
old_x = e.clientX
old_index = current_index
window.addEventListener("pointermove", onScrub)
}
el.addEventListener("pointerdown", onpointerdown)


const onpointerup = () => {
window.removeEventListener("pointermove", onScrub)
}
window.addEventListener("pointerup", onpointerup)

el.onselectstart = () => false

invalidation.then(() => {
el.removeEventListener("pointerdown", onpointerdown)
window.removeEventListener("pointerup", onpointerup)
})

return el

</script>""")
end
Scrubbable
end
👀 Reading hidden code
44.4 ms
export Scrubbable
👀 Reading hidden code
83.6 μs